package com.gitee.huanminabc.utils_common.media.img;

import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * 使用 Thumbnailator 进行图像压缩的工具类。
 *
 * <p>核心目标：尽可能少的压缩轮数将任意大小的图片压缩到指定大小以下。</p>
 */
@Slf4j
public class ThumbnailatorUtil {

    /**
     * 最大压缩轮数，避免压缩时间过长
     */
    private static final int MAX_COMPRESSION_ROUNDS = 5;

    /**
     * 按照目标大小（单位：KB）将图像压缩到不超过此大小。
     *
     * @param imageBytes       源图像的字节数组
     * @param targetFileSizeKB 期望的图像大小（单位：KB）
     * @return 压缩后的图像字节数组；如果压缩失败或超过最大轮数仍无法达到目标，则返回当前最优压缩结果
     * @throws IllegalArgumentException 如果输入无效
     */
    public static byte[] compressToSize(byte[] imageBytes, long targetFileSizeKB) {
        long start = System.currentTimeMillis(); // 记录方法开始时间

        // 1. 参数和状态检查
        if (imageBytes == null || imageBytes.length == 0 || targetFileSizeKB <= 0) {
            throw new IllegalArgumentException("图像压缩的输入参数无效。");
        }
        // 如果原图已经小于目标大小，则直接返回
        if (imageBytes.length <= targetFileSizeKB * 1024) {
            log.info("原图已小于或等于目标大小，直接返回，无需压缩。");
            return imageBytes;
        }

        // 2. 初始变量准备
        byte[] compressedImageBytes = imageBytes;
        long originalSize = imageBytes.length;
        long targetSizeBytes = targetFileSizeKB * 1024;
        log.info("开始基于大小的图像压缩。原始大小: {} 字节, 目标大小: {} 字节", originalSize, targetSizeBytes);
        try {
            // 3. 开始正式压缩
            int compressionRounds = 0;
            long compressionStart = System.currentTimeMillis();

            // 在循环中不断计算 ratio，以在尽量少的轮数中压到目标大小
            while (compressedImageBytes.length > targetSizeBytes && compressionRounds < MAX_COMPRESSION_ROUNDS) {
                // 计算本轮压缩精度
                double accuracy = calculateAccuracy(compressedImageBytes.length, targetSizeBytes, compressionRounds);
                log.info("第 {} 轮压缩，当前图像大小: {} 字节，目标大小: {} 字节，计算出的 accuracy: {}",
                        compressionRounds + 1, compressedImageBytes.length, targetSizeBytes, accuracy);

                // 执行压缩
                try (ByteArrayInputStream inputStream = new ByteArrayInputStream(compressedImageBytes);
                     ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {

                    Thumbnails.of(inputStream)
                            .scale(0.85f)
                            .outputFormat("jpg")
                            .outputQuality(accuracy) // 调整质量
                            .toOutputStream(outputStream);

                    compressedImageBytes = outputStream.toByteArray();
                    compressionRounds++;
                }
            }

            long compressionEnd = System.currentTimeMillis();
            log.info("图像压缩环节耗时: {} ms", compressionEnd - compressionStart);

            // 4. 压缩结果总结
            if (compressionRounds == MAX_COMPRESSION_ROUNDS && compressedImageBytes.length > targetSizeBytes) {
                log.warn("已达到最大压缩轮数({})，但仍未能将图像压缩至目标大小。返回当前最优结果。", MAX_COMPRESSION_ROUNDS);
            } else {
                log.info("基于大小的压缩完成，共进行 {} 轮压缩。原始大小: {} 字节, 压缩后大小: {} 字节",
                        compressionRounds, originalSize, compressedImageBytes.length);
            }

            long end = System.currentTimeMillis();
            log.info("compressToSize 方法总耗时: {} ms", end - start);

            return compressedImageBytes;

        } catch (IOException e) {
            log.error("图像压缩过程中发生异常: ", e);
            return imageBytes; // 如果发生错误，返回原始图像
        }
    }

    /**
     * 根据当前图像大小、目标大小以及当前轮数计算下一轮的压缩精度（outputQuality）。
     *
     * @param currentSize      当前图像的大小（单位：字节）
     * @param targetSize       目标图像的大小（单位：字节）
     * @param compressionRound 已进行的压缩轮数（从 0 开始计）
     * @return 下一轮压缩应使用的 outputQuality 值，取值区间 [0.1, 1.0]
     */

    private static double calculateAccuracy(long currentSize, long targetSize, int compressionRound) {
        double ratio = (double) targetSize / currentSize;

        // 如果当前图已经比目标小，就不再继续压缩
        if (ratio >= 1.0) {
            return 1.0;
        }

        // 设定一个最小 quality
        double minQuality = 0.1;
        // 设定一个最大 quality
        double maxQuality = 0.8;

        // 第一次压缩尽量直接用 ratio。比方说 ratio=0.65，就先用 0.65
        // 如果第一轮没有压到位，再逐轮乘以一个小于 1 的衰减系数
        double base = ratio;
        double decay = 0.9; // 每轮再乘以 0.9，进一步降低质量

        double accuracy = base * 0.4 * Math.pow(decay, compressionRound);

        // 做边界限制
        if (accuracy < minQuality) accuracy = minQuality;
        if (accuracy > maxQuality) accuracy = maxQuality;

        return accuracy;
    }

    /**
     * 按固定百分比（默认 50%）压缩图像。
     *
     * @param imageBytes 源图像的字节数组
     * @return 压缩后的图像作为 InputStream；如果压缩失败则返回原始图像
     * @throws IllegalArgumentException 如果输入无效
     */
    public static InputStream compressByPercentage(byte[] imageBytes) {
        return compressByPercentage(imageBytes, 0.5);
    }

    /**
     * 按指定百分比压缩图像。
     * <p>例如，percentage = 0.5 表示将图像大小压到原先的 50% 左右。</p>
     *
     * @param imageBytes 源图像的字节数组
     * @param percentage 压缩百分比 [0,1)，例如 0.5
     * @return 压缩后的图像作为 InputStream；如果压缩失败则返回原始图像
     * @throws IllegalArgumentException 如果输入无效
     */
    public static InputStream compressByPercentage(byte[] imageBytes, double percentage) {
        if (imageBytes == null || imageBytes.length == 0 || percentage <= 0 || percentage >= 1) {
            throw new IllegalArgumentException("百分比压缩的输入参数无效。");
        }

        // 将目标大小换算为 KB，然后调用基于大小的压缩逻辑
        long targetSizeBytes = (long) (imageBytes.length * percentage);
        long targetSizeKB = targetSizeBytes / 1024;
        byte[] compressedBytes = compressToSize(imageBytes, targetSizeKB <= 0 ? 1 : targetSizeKB);
        return new ByteArrayInputStream(compressedBytes);
    }

    public static void main(String[] args) throws IOException {
        long start = System.currentTimeMillis();
        log.info("开始压缩测试...");

        // 读取本地图片
        Path path = Paths.get("/Users/wangjie/Pictures/测试图/压缩后/compressed.jpg");
        byte[] imageBytes = Files.readAllBytes(path);

        // 示例：将图片压缩到不超过 2MB
        byte[] result = compressToSize(imageBytes, 2048);

        // 写入压缩后的文件
        Files.write(path.resolveSibling("compressed-1.jpg"), result);

        long end = System.currentTimeMillis();
        log.info("测试完成，总耗时: {} ms", end - start);
        System.out.println("压缩完成，总耗时: " + (end - start) + " ms");
    }
}
